/**
*
* @file
*		heap_test_av
*
* @brief
*		Tests for heap by anyav@
*/


#include <stdlib.h>
#include <stdio.h>
#include "../../api/heap.h"
#include "../../src/senya/heap_senya.h"



#ifndef PAGE_SIZE
#define PAGE_SIZE 4096
#endif

#ifndef max
#define max(a,b) ( (a) > (b) ? (a) : (b) )
#endif


/**
 * SEH allows to catch more errors. use it if available. Wrappers are below
 */
#ifdef _WIN32
#define TRY __try
#define CATCH __except(EXCEPTION_EXECUTE_HANDLER)
#else
#pragma message("*** Cannot use SEH. crashes of allocator are likely to break test");
#define TRY try
#define CATCH catch(...)
#endif


/**
 * Minimal test binding. All of these macro require existence of res and passed variable
 * and "_fin" mark
 */
#define START_TEST(name)								\
do{														\
	char* test_name = name;								\
	TRY{															

#define END_TEST()										\
		passed++;										\
	}CATCH												\
	{													\
		res = false;									\
		printf("*** Test:(%30s) excepted\n", test_name );\
	};													\
}while(0) // local test variables

#define TEST_FAILED()									\
	do{													\
		printf("*** Test (%30s) failed\n", test_name );	\
		res = false;									\
		goto _fin;										\
	}while(0)


/**
 * @brief
 *	test general functionality
 * 
 * @return
 *		true on success
 */
bool general_test( heap876* hp )
{
	size_t i, j;
	void** ptr = NULL;
	bool coherent = true;
	size_t internal_overhead, max_allocation;
	static size_t max_block_size = 64;
	bool res = true;
	int passed = 0;

	printf("Analyzing allocator %s<0x%p kb>\n", hp->get_name(), hp->get_volume()/1024 );

	START_TEST("occupancy"); // check for zero occupancy
	if( hp->get_occupancy() != 0 )
		TEST_FAILED();
	END_TEST();

	// allocate more then required but this array will allow to store all allocations
	START_TEST("alloc service pointers array");
	ptr = (void**)malloc( sizeof(void*) * hp->get_volume() );
    if( !ptr )
		TEST_FAILED();
	END_TEST();

	// make a lot of 1-bytes alloc
	START_TEST("1-byte allocations");
	for( i = 0; ptr[i] = hp->alloc( 1, 0 ); i++ ) ;
	if( i == 0 )
		TEST_FAILED();
	END_TEST();

	START_TEST("releasing of 1-byte allocations");
	for( j = 0; j < i; j++ )
		hp->free( ptr[j] );
	END_TEST();

	internal_overhead = (hp->get_volume() - i)/i; // ignore round-off
	max_allocation = hp->get_volume()/( (max_block_size + sizeof(int)/2) + internal_overhead + 1 );
	printf("Service info stored within heap = %u bytes, make %u allocations\n", 
		(unsigned int)internal_overhead, (unsigned int)max_allocation );

	START_TEST("Allocating max");
    for( i = 0; i < max_allocation; i++ )
	{
		ptr[i] = hp->alloc( max( (i % max_block_size), sizeof(int) ), 2 );
		if( NULL == ptr[i] )
		{	
			printf("Failed to satisfy our request [0x%x of 0x%x]\n", i,hp->get_volume() );
			for( j = 0; j < i; j++ )
				hp->free( ptr[j] );
			TEST_FAILED();
		}	
		*(int*)ptr[i] = (int)i;
	}
	END_TEST();

	START_TEST("Releasing and verifying max");
	for( j = 0; j < i; j++ )
	{
		if( *(int*)ptr[j] != j )
		{
			printf("Inconsistency[%p]. instead of expected = %x\n",
				j, *(int*)ptr[j] );
			TEST_FAILED();
		}
		hp->free( ptr[j] );
	}
	END_TEST();
		
	START_TEST("Verifying free_all");
	hp->free_all(0);
	if( hp->get_occupancy() != 0 )
		TEST_FAILED();
	END_TEST();

_fin:
	printf("%s general test %s (passed %u)\n", res?"Completed":"FAILED",
		hp->get_name(), passed );
	if( ptr )
		free( ptr );
	hp->free_all(0);
	return res;
}

/**
 * this test is aimed to test general functionality
 */
bool stress_test( heap876* hp )
{
	bool res;
	int passed = 0;

	printf("Stress testing of allocator %s\n", hp->get_name() );	

	START_TEST("alloc above max");
	if( hp->alloc( hp->get_volume() + 1 ) )
		TEST_FAILED();
	END_TEST();

	START_TEST("alloc zero");
	hp->alloc( 0 );
	END_TEST();

	START_TEST("free null");
	hp->free( NULL );
	END_TEST();

	START_TEST("free invalid pointer");
	hp->free( (void*)0xdeadbaba );
	END_TEST();

_fin:
	printf("%s stress test %s (passed %u)\n", res?"Completed":"FAILED",
		hp->get_name(), passed );
	return res;
}
	
bool sync_test( heap876* hp )
{
	printf("Testing synchronization at allocator %s", hp->get_name() );		
	return 0;
}

/**
 * Here we should carefully read logging and control output of allocator
 */
bool debug_test( heap876* hp )
{
	return true;
}

/**
 * execution of test above your class
 */
int main( int argc, char* argv[] )
{
	bool ret = true;

	{
		heap_senya hp( 16 * PAGE_SIZE, heap876::alloc_simple );
		ret &= general_test( &hp );
		ret &= stress_test( &hp );
		ret &= sync_test( &hp );
	}
	{
		heap_senya hp_debug( 512 * PAGE_SIZE, heap876::alloc_debug );
		ret &= debug_test( &hp_debug );
	}

	printf("The test set has completed with %s result", ret?"success":"failed" );

	return (int)!ret;
}
